“컬렉션 패치조인 페이징 하기”
엔티티와 샘플 데이터
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Entity @Table(name = "orders") public class Order {
@Id @GeneratedValue @Column(name = "order_id") private Long id;
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) private Delivery delivery; @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) private List<OrderItem> orderItems = new ArrayList<>(); }
|
OrderItem.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Entity public class OrderItem {
@GeneratedValue @Id @Column(name = "order_item_id") private Long id;
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id") private Order order;
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "item_id") private Item item; }
|
샘플 데이터
1 2 3 4 5 6 7 8 9 10 11
| @Entity public abstract class Item {
@Id @GeneratedValue @Column(name = "item_id") private Long id; }
|
컬렉션 패치 조인
- 컬렉션을 패치 조인 하면 일대다 조인이 발생하므로 데이터가 예측할수 없이 증가.
페이징을 추가한다면?
1 2 3 4 5 6 7 8 9 10 11
| public List<Order> findAllWithItem() { return em.createQuery( "select distinct o from Order o" + " join fetch o.member m" + " join fetch o.delivery d" + " join fetch o.orderItems oi" + " join fetch oi.item i", Order.class ).setFirstResult(1) .setMaxResults(100) .getResultList(); }
|
- 하이버네이트는 경고 로그를 남김.
- DB 데이터를 읽어 메모리에서 페이징을 시도.
왜?
- 패치조인 후 DB에서 페이징 처리 시 다의 관계 데이터 기준으로 페이징 하게 되고 이는 의도한 바와 다르기 때문.
- 메모리에 데이터를 다 들고 오게 되므로 최악의 경우 장애로 이어질 수 있음.
해결책
@BatchSize, default_batch_fetch_size
다음의 순서로 컬렉션 패치 조인과 페이징 진행
- XToOne 관계 패치 조인
- row 수 증가 시키지 않으므로 페이징 쿼리에 영향 주지 않음
- 컬렉션 지연 로딩 조회
- hibernate.default_batch_fetch_size, @BatchSize 적용
- 프록시 객체를 한번에 설정한 size 만큼 in 쿼리로 조회